﻿using Jacobi.Vst.Framework;
using Jacobi.Vst.Core;
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Aleator.Resources;
using Aleator.Helpers;

namespace Aleator.Core
{
    /// <summary>
    /// Phrase class for bass type melodies
    /// </summary>
    public class BassPhrase : ConcretePhrase
    {
        #region Members

        private int m_bassOctave = Session.HomeOctave.Value - 1;

        #endregion

        #region Constructors
        /// <summary>
        /// Main constructor
        /// </summary>
        /// <param name="progression">Progression that phrase is being attached to</param>
        /// <param name="basephrase">Phrase to use in populating this phrase's note list(s)</param>
        public BassPhrase(ConcreteProgression progression, BassPhrase basephrase = null)
        {

            m_progression = progression;
            Notes = new List<ConcreteNote>();
            m_homeOctaveNotes = new List<ConcreteNote>();

            if (basephrase == null)
            {
                m_noteDistribution = (Distribution)Enum.ToObject(typeof(Distribution), Session.RndGenerator.Next(2));

                SetTotals();
            }
            else
            {
                m_noteDistribution = basephrase.m_noteDistribution;

                CopyToPhrase((ConcretePhrase)basephrase);
            }

            Progression.Notes.AddRange(m_notes);
        }

        /// <summary>
        /// Bass phrase constructor allowing for variance from a progression group
        /// </summary>
        /// <param name="progression"></param>
        /// <param name="basephrase"></param>
        /// <param name="variance"></param>
        public BassPhrase(ConcreteProgression progression, int variance)
        {
            m_progression = progression;

            //If there is no basephrase, we are generating original note lists. Otherwise, initiate copying procedure
            if (!progression.Composition.Progressions.Any())
            {
                throw new NotImplementedException("Variance requires at least one progression in the current composition!");
            }
            else
                Commonality.Explode(this);

            Progression.Notes.AddRange(m_notes);

        }

        #endregion

        #region Methods

        /// <summary>
        /// Determine how many notes are in the phrase
        /// </summary>
        /// <param name="sigma">Determines the shape of the bell curve when using normal distribution</param>
        protected override void SetTotals(double sigma = 2.0)
        {
            if (NoteDistribution != Distribution.Rigid)
            {
                base.SetTotals();
                m_noteTotal = GetTotal(m_mu, m_sigma);
            }
            else
            {
                //Always use continuous for rigid. Allow for a few missing notes (one per measure)
                m_continuous.Beta = Progression.TotalBeats - 1;
                m_continuous.Alpha = Progression.TotalBeats - Progression.TotalMeasures;
                m_noteTotal = (int)m_continuous.NextDouble();
            }

            m_noteOnMessageNumber = Convert.ToInt16(AppResources.BassOnMessageID);
            m_noteOffMessageNumber = Convert.ToInt16(AppResources.BassOffMessageID);
            Generate(2);
        }

        /// <summary>
        /// Determine method for populating note lists based on distribution from XML
        /// </summary>
        /// <param name="resolution"></param>
        protected override void Generate(int resolution)
        {
            m_resolution = resolution;

            //No loose bass lines it sounds like shit.
            if (NoteDistribution == ConcretePhrase.Distribution.Rigid)
                Rigid();
            else
                Tight();
        }

        /// <summary>
        /// This method will produce a melodic line that repreats every measure
        /// </summary>
        protected override void Tight()
        {
            m_continuous.Beta = PlayableNotesInMeasure - 1;
            m_continuous.Alpha = 0;

            m_rayleigh.Sigma = 3.0;

            //inner counter will be re-used to add remaining notes into the phrase below
            int notesInMeasure = m_noteTotal / m_progression.TotalMeasures;

            int j = 0;
            int beatbase = 0;
            int counter = 0;

            Chord c;
            ConcreteNote n;

            m_measure = 0;

            //Fill out the first measure of the phrase
            while (j < notesInMeasure)
            {
                GetBeat();

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!m_notes.Where(k => k != null && k.Location == m_location && k.Measure == 0).Any())
                {
                    //Pick a note from the appropriate chord in the phrase
                    //TODO: Make sure we are accounting for octaves - we'll stay within 3 to mimic the bass guitar
                    c = Progression.Chords.Where(ch => ch.Key <= m_location / m_resolution).OrderByDescending(ch => ch.Key)
                        .Select(ch => ch.Value).First();

                    //If there is only one chord we are going to only play the root for support
                    if (Progression.Chords.Count > 1)
                        m_homeOctaveNotes.AddRange(c.Notes.Where(k => k.Octave == Session.LowOctave));
                    else
                        m_homeOctaveNotes.AddRange(c.Roots);

                    n = m_homeOctaveNotes[Session.RndGenerator.Next(m_homeOctaveNotes.Count)];
                    m_noteNum = n.NoteID;

                    ChordInterval = c.GetChordInterval(n);

                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    m_notes.Add((ConcreteNote)m_notefactory.GetNote(m_location, 0, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this, c.DisplayName));
                    j++;

                    m_homeOctaveNotes.Clear();
                }
            }

            //Fill out the rest
            for (int i = 1; i < m_progression.TotalMeasures; i++)
            {
                beatbase = i * m_progression.Numerator;

                if (j > m_progression.TotalMeasures * notesInMeasure)
                    break;
                try
                {
                    for (int k = 0; k < notesInMeasure; k++)
                    {
                        m_location = m_notes[j % notesInMeasure].Location;

                        c = Progression.Chords.Where(ch => ch.Key <= beatbase + (m_location / m_resolution)).OrderByDescending(ch => ch.Key)
                            .Select(ch => ch.Value).First();

                        if (Progression.Chords.Count > 1)
                            m_homeOctaveNotes.AddRange(c.Notes.Where(o => o.Octave == Session.LowOctave));
                        else
                            m_homeOctaveNotes.AddRange(c.Roots);

                        n = m_homeOctaveNotes[Session.RndGenerator.Next(m_homeOctaveNotes.Count)];
                        ChordInterval = c.GetChordInterval(n);

                        m_notes.Add((ConcreteNote)m_notefactory.GetNote(m_location, i, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this, c.DisplayName));
                        j++;

                        m_homeOctaveNotes.Clear();
                    }
                }
                catch (IndexOutOfRangeException)
                {
                    //TODO: Remove try/catch block
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = m_notes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < PlayableNotesInMeasure; i++)
                {
                    if (fullnotes.Where(k => k.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Count > 0)
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < m_noteTotal)
                {
                    m_measure = Session.RndGenerator.Next(m_progression.TotalMeasures);

                    if (counter == m_progression.TotalMeasures)
                    {
                        counter = 0;
                        m_location = beats[Session.RndGenerator.Next(beats.Count)];
                    }

                    if (m_notes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        beatbase = m_measure * m_progression.Numerator;

                        c = Progression.Chords.Where(ch => ch.Key <= beatbase + (m_location / m_resolution)).OrderByDescending(ch => ch.Key)
                            .Select(ch => ch.Value).First();

                        if (Progression.Chords.Count > 1)
                            m_homeOctaveNotes.AddRange(c.Notes.Where(k => k.Octave == Session.LowOctave));
                        else
                            m_homeOctaveNotes.AddRange(c.Roots);

                        n = m_homeOctaveNotes[Session.RndGenerator.Next(m_homeOctaveNotes.Count)];
                        ChordInterval = c.GetChordInterval(n);

                        //m_notes[j] = new Note(beat, measure, n.NoteID, m_noteOnMessageNumber, m_noteOffMessageNumber, this);
                        m_notes.Add((ConcreteNote)m_notefactory.GetNote(m_location, m_measure, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this, c.DisplayName));
                        j++;

                        m_homeOctaveNotes.Clear();
                    }

                    counter++;
                }
            }

            AddRests();
        }

        /// <summary>
        /// This method will produce one melodic line over the entore length of the phrase
        /// </summary>
        protected override void Loose()
        {
            //Set the top of the note distribution range equal the highest quantized note value
            //Alpha = least amount of possible notes, Beta is the max
            m_continuous.Beta = PlayableNotesInMeasure - 1;
            m_continuous.Alpha = 0;

            //Set the top of the measure distribution range equal the highest measure value
            m_continuousMeasure.Beta = m_progression.TotalMeasures - 1;
            m_continuousMeasure.Alpha = 0;

            //Set sigma equal to half the numerator for rayleigh distribution
            m_rayleigh.Sigma = 3.0;

            //inner counter will be re-used to add remaining notes into the phrase below
            int j = 0;
            int beatbase;

            Chord c;
            ConcreteNote n;

            //Just iterate through the list of notes and assign them
            while (j < m_noteTotal)
            {
                m_measure = (int)Math.Round(m_continuousMeasure.NextDouble());

                GetBeat();

                //If a note with this number and measure has not already been selected, we can add it it to the note array
                if (!m_notes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Any())
                {
                    beatbase = m_progression.Numerator * m_measure;

                    //Pick a note from the appropriate chord in the phrase
                    //Stay in the home octave for bass lines. Sounds like shit otherwise.
                    c = Progression.Chords.Where(ch => ch.Key <= beatbase + m_location).OrderByDescending(ch => ch.Key)
                        .Select(ch => ch.Value).First();
                    m_homeOctaveNotes.AddRange(c.Notes.Where(k => k.Octave == Session.LowOctave));
                    n = m_homeOctaveNotes[Session.RndGenerator.Next(m_homeOctaveNotes.Count)];
                    ChordInterval = c.GetChordInterval(n);
                    m_noteNum = n.NoteID;

                    //Create the new note and add it to the notes array, passing in the beat, measure (i) and note numbers
                    //m_notes[j] = new Note(beat, measure, m_noteNum, m_noteOnMessageNumber, m_noteOffMessageNumber, this);
                    m_notes.Add((ConcreteNote)m_notefactory.GetNote(m_location, m_measure, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this, c.DisplayName));
                    j++;

                    m_homeOctaveNotes.Clear();
                }
            }

            AddRests();
        }

        /// <summary>
        /// This method will produce a melodic line that will play on almost every quantized note with limited note pitch variation.
        /// </summary>
        protected override void Rigid()
        {
            //First we need to determine where the missing notes fall
            int missingbeat = Session.RndGenerator.Next(PlayableNotesInMeasure);
            int[] missingmeasures = new int[Session.RndGenerator.Next(Progression.TotalMeasures)];

            Chord c;
            Chord.ScaleDegree degree = Chord.ScaleDegree.I;
            ConcreteNote n = (ConcreteNote)m_notefactory.GetNote();

            int beatbase;
            int i = 0;

            while (i < missingmeasures.Count())
            {
                m_measure = Session.RndGenerator.Next(Progression.TotalMeasures);
                if (!missingmeasures.Contains(m_measure))
                {
                    missingmeasures[i] = m_measure;
                    i++;
                }
            }

            //Iterate through all the measures
            for (int m = 0; m < Progression.TotalMeasures; m++)
            {
                beatbase = m_progression.Numerator * m;

                //Iterate through the playable notes in a measure
                for (int b = 0; b < PlayableNotesInMeasure; b++)
                {
                    //If this beat/measure combo wasnt selected for omission, we can add it
                    if (!missingmeasures.Contains(m) && missingbeat != b)
                    {
                        c = Progression.Chords.Where(ch => ch.Key <= beatbase + b).OrderByDescending(ch => ch.Key)
                            .Select(ch => ch.Value).First();

                        //Only change notes if we are changing chords as well
                        if (c.Degree != degree)
                        {
                            m_homeOctaveNotes.AddRange(c.Notes.Where(k => k.Octave == Session.LowOctave));
                            n = m_homeOctaveNotes[Session.RndGenerator.Next(m_homeOctaveNotes.Count)];
                            ChordInterval = c.GetChordInterval(n);
                            m_noteNum = n.NoteID;

                            m_homeOctaveNotes.Clear();
                        }

                        m_notes.Add((ConcreteNote)m_notefactory.GetNote(b, m, n, m_noteOnMessageNumber, m_noteOffMessageNumber, this, c.DisplayName));

                        degree = c.Degree;
                    }
                }
            }

            AddRests();
        }

        #endregion
    }
}
